Skip to content

[CP-127] add exchange v2 support #263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
May 22, 2025
Merged

[CP-127] add exchange v2 support #263

merged 23 commits into from
May 22, 2025

Conversation

aarmoa
Copy link
Collaborator

@aarmoa aarmoa commented Nov 14, 2024

  • Added support for all queries and messages from the new Exchange V2.
  • Applied the required updates for the chain stream
  • Deprecated all methods from Exchange V1 in favor of the ones from Exchange V2.
  • Updated all example scripts using Exchange V1 elements to use Exchange V2.
  • Updated unit tests.

Solves CP-127

Summary by CodeRabbit

  • New Features

    • Introduced Ethereum Virtual Machine (EVM) module types, including Ethereum transaction handling, chain configuration, error handling, event constants, storage utilities, and log conversion.
    • Added generic exchange authorization for flexible message permissions with spend limits.
    • Introduced new utility functions for price, quantity, and notional conversions between chain and human-readable formats.
  • Improvements

    • Enhanced concurrency safety for authorized market limits.
    • Updated validation and error messages for better clarity and stricter checks.
    • Modernized and clarified parameter validation and configuration structures.
  • Removals

    • Removed deprecated message types, proposal constructors, position and order delta logic, fee discount tier calculations, and various accessor and utility methods.
    • Pruned legacy or redundant code related to market and order management, position handling, and genesis state creation.
  • Bug Fixes

    • Fixed address validation and duplicate detection in order and batch update messages.
    • Improved handling of edge cases in price parsing and validation logic.
  • Chores

    • Updated linter configurations and Makefile dependencies for improved development workflow.
    • Refactored code for consistency, readability, and maintainability.

Copy link

coderabbitai bot commented Nov 14, 2024

Walkthrough

This update introduces a significant refactor and expansion of the codebase, particularly in the EVM module and exchange types. The EVM module receives a comprehensive implementation of transaction, log, error, parameter, and utility types, enabling robust Ethereum compatibility. The exchange types are streamlined, with many legacy methods and types removed, new concurrency-safe limits, and improved validation and utility functions. Several deprecated message types and proposal constructors are removed, and configuration files are modernized.

Changes

File(s) / Path(s) Change Summary
.github/workflows/golangci-lint.yml, .golangci.yml, Makefile Updated workflow and config versions, improved linter settings, updated Makefile targets, added lint-all target.
chain/crypto/ethsecp256k1/ethsecp256k1.go, chain/crypto/hd/algorithm.go Refactored method receiver names for consistency; updated comments.
chain/crypto/hd/hd_path.go New: Introduces HD wallet path constants and utilities for Ethereum.
chain/erc20/types/codec.go New: Adds codec registration for ERC20 module messages and interfaces.
chain/evm/types/ (access_list.go, access_list_tx.go, chain_config.go, codec.go, dynamic_fee_tx.go, errors.go, eth.go, events.go, key.go, legacy_tx.go, logs.go, msg.go, params.go, storage.go, tx.go, tx_data.go, utils.go) New: Implements a full EVM types module: transaction types, codecs, error handling, logs, params, storage, and utilities for Ethereum compatibility.
chain/exchange/types/authz_common.go, authz_derivative.go, authz_spot.go Adds concurrency-safe access for authorized markets limit; updates validation logic to use accessor function.
chain/exchange/types/authz_exchange_generic.go New: Adds generic exchange authorization type with spend limit checks.
chain/exchange/types/codec.go Removes deprecated message types from codec registration; adds new authorization type.
chain/exchange/types/common_order.go Refactors and simplifies order margin logic, adds new public methods, and exports order hash computation.
chain/exchange/types/common_utils.go Removes order delta types and methods; introduces generic duplicate detection functions.
chain/exchange/types/deposit.go Removes Deposit constructors and display methods.
chain/exchange/types/derivative_orders.go Removes extensive business logic for derivative orders; retains only minimal test utilities and basic getters.
chain/exchange/types/errors.go Updates and adds error messages and disables line-length linting.
chain/exchange/types/exchange.go Removes multiple order, market, and reward types/methods; adds minimal getters and a new validation method.
chain/exchange/types/fee_discounts.go Removes all fee discount schedule methods and constructors.
chain/exchange/types/fee_validation.go Reformats function signatures for clarity; adds test code comment.
chain/exchange/types/genesis.go Removes genesis state constructor functions.
chain/exchange/types/key.go Deprecates legacy prefixes, adds new expiration prefixes, and improves price parsing.
chain/exchange/types/market.go Removes all market accessor methods; adds standalone conversion utilities for price, quantity, notional.
chain/exchange/types/market_admin.go Removes permission-checking methods; corrects and adds permission constants.
chain/exchange/types/msgs.go Removes deprecated messages; introduces interface for spot market updates, centralizes address validation, and improves duplicate detection.
chain/exchange/types/msgs_validation.go New: Adds validation function for spot market update messages.
chain/exchange/types/params.go Renames and exports validation functions; removes parameter constructors and defaults.
chain/exchange/types/positions.go Removes all position logic, leaving only an exported payout struct.
chain/exchange/types/proposal.go Removes all proposal constructor functions; tightens margin validation.
auth_vote/authz_vote.go Removes explicit websocket path from Tendermint RPC client initialization.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ExchangeTypes
    participant EVMTypes
    participant CosmosSDK

    User->>ExchangeTypes: Submit order / message
    ExchangeTypes->>ExchangeTypes: Validate input, check limits, deduplicate
    ExchangeTypes->>EVMTypes: (if EVM tx) Convert/validate Ethereum tx
    EVMTypes->>CosmosSDK: Register codecs, handle tx, log, error types
    CosmosSDK-->>User: Respond with tx result or error
Loading
sequenceDiagram
    participant EVMTypes
    participant Ethereum
    participant CosmosSDK

    EVMTypes->>Ethereum: Convert Cosmos tx to Ethereum tx format
    Ethereum-->>EVMTypes: Return tx data, logs, errors
    EVMTypes->>CosmosSDK: Register interfaces, pack/unpack Any, handle params
    CosmosSDK-->>EVMTypes: Provide context for validation and execution
Loading

Poem

🐰
A hop, a skip, a code refactor,
EVM and exchange, both now sharper!
Old types retired, new paths unfurled,
With codecs and keys, a blockchain world.
Linting is modern, configs aligned,
A rabbit’s delight—such progress combined!

((\
( -.-)
o_(")(")

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (1.64.8)

Error: you are using a configuration file for golangci-lint v2 with golangci-lint v1: please use golangci-lint v2
Failed executing command with error: you are using a configuration file for golangci-lint v2 with golangci-lint v1: please use golangci-lint v2

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

socket-security bot commented Apr 30, 2025

@aarmoa aarmoa removed the request for review from PavelInjective May 1, 2025 06:55
@maxim-inj
Copy link
Collaborator

Is this stale?

@aarmoa
Copy link
Collaborator Author

aarmoa commented May 7, 2025

Is this stale?

No, it is just work in progress, not stale

@aarmoa aarmoa mentioned this pull request May 13, 2025
… endpoint (the /websocket destination is added internally now)
@aarmoa aarmoa changed the base branch from dev to v1.58.x-evm-comet1 May 15, 2025 18:56
@aarmoa aarmoa changed the base branch from v1.58.x-evm-comet1 to master May 15, 2025 18:56
@aarmoa aarmoa changed the base branch from master to dev May 15, 2025 18:57
@aarmoa aarmoa marked this pull request as ready for review May 22, 2025 19:09
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 23

🧹 Nitpick comments (34)
chain/erc20/types/codec.go (1)

13-14: Exported constant lacks a doc-comment

golangci-lint (with golint / revive) will flag exported identifiers that don’t start with a comment matching the identifier’s name.
Add a short sentence to avoid lint noise.

-const ModuleName = "erc20"
+// ModuleName defines the ERC-20 module’s name.
+const ModuleName = "erc20"
chain/exchange/types/errors.go (1)

53-54: Prefer user-friendly wording for ErrMarginsRelation message
The error message currently embeds Go-style field names (MaintenanceMarginRatio, InitialMarginRatio, …). End-users seldom see or understand struct field names; consider re-phrasing with plain English, e.g.
"maintenance margin ratio must be lower than initial margin ratio, which must not exceed reduce margin ratio".
This keeps the API surface unchanged while improving UX and documentation.

chain/exchange/types/fee_validation.go (1)

37-44: Move “test-only” helper behind a build tag
ValidateMakerWithTakerFeeAndDiscounts is annotated as “Test Code Only (for v1 tests)”, yet it ships in production builds.
A lightweight way to keep the helper while avoiding dead-code detectors and API exposure:

-// Test Code Only (for v1 tests)
+//go:build testutils
+// +build testutils
+// Test Code Only (for v1 tests)

Then place the file in a *_testutils.go or similar file.
This keeps the symbol out of release binaries but available to tests.

chain/evm/types/events.go (1)

3-24: Exported constants lack doc comments – may trigger linters
golint, revive, and similar tools usually require a comment for every exported identifier. Adding a short description avoids CI noise:

-const (
-	EventTypeEthereumTx = TypeMsgEthereumTx
+// EventTypeEthereumTx is emitted for every processed Ethereum-style transaction.
+const (
+	EventTypeEthereumTx = TypeMsgEthereumTx

Repeat for the remaining exported constants as needed.

chain/exchange/types/market_admin.go (1)

4-13: Bit-flag enumeration is correct but can be simplified
Using 1 << iota on every line works, yet the idiomatic pattern sets it once and relies on blank identifiers:

-const (
-	TickerPerm                 = 1 << iota
-	MinPriceTickSizePerm       = 1 << iota
-	MinQuantityTickSizePerm    = 1 << iota
-	MinNotionalPerm            = 1 << iota
-	InitialMarginRatioPerm     = 1 << iota
-	MaintenanceMarginRatioPerm = 1 << iota
-	ReduceMarginRatioPerm      = 1 << iota
+const (
+	_                         = 1 << iota // skip value 1<<0 if unused
+	TickerPerm
+	MinPriceTickSizePerm
+	MinQuantityTickSizePerm
+	MinNotionalPerm
+	InitialMarginRatioPerm
+	MaintenanceMarginRatioPerm
+	ReduceMarginRatioPerm
 )

Same values, less repetition and slightly clearer intent.

chain/exchange/types/key.go (3)

104-108: Consider removing commented-out code instead of preserving it

Rather than commenting out the legacy prefixes, it would be better to completely remove them if they're no longer used. Commented-out code can lead to confusion and maintenance issues over time.

-	// old legacy prefixes
-	// SpotConditionalMarketOrdersPrefix            = []byte{0x70} // prefix for a key to save conditional spot market orders: marketID + direction + triggerPrice + orderHash ⇒ spotMarketOrder
-	// SpotConditionalMarketOrdersIndexPrefix       = []byte{0x71} // prefix for a key to save conditional spot market orders index: marketID + direction + subaccountID + orderHash ⇒ triggerPrice
-	// SpotConditionalLimitOrdersPrefix             = []byte{0x72} // prefix for a key to save conditional spot limit orders: marketID + direction + triggerPrice + orderHash ⇒ spotLimitOrder
-	// SpotConditionalLimitOrdersIndexPrefix        = []byte{0x73} // prefix for a key to save conditional spot limit orders index: marketID + direction + subaccountID + orderHash ⇒ triggerPrice

123-124: Add comments to describe the purpose of the new prefixes

The new prefixes lack descriptive comments explaining their purpose and usage. Adding comments similar to the other prefix constants would improve code readability and maintainability.

-	MarketBalanceKey             = []byte{0x84} // key for each key to a MarketBalance
-	OrderExpirationsPrefix       = []byte{0x85} // prefix to store order expirations
-	OrderExpirationMarketsPrefix = []byte{0x86} // prefix to store markets with order expirations
+	MarketBalanceKey             = []byte{0x84} // key for each key to a MarketBalance
+	OrderExpirationsPrefix       = []byte{0x85} // prefix to store order expirations by block number and market ID
+	OrderExpirationMarketsPrefix = []byte{0x86} // prefix to store markets with order expirations by block number

570-590: Well-implemented key construction functions

The new key construction functions follow the pattern of other similar functions in the file. They properly allocate buffers of the right size for efficiency and use consistent naming conventions.

Consider adding a function to parse these keys back into their components for completeness:

func ParseOrderExpirationPrefix(key []byte) (blockNumber int64, marketID common.Hash) {
	prefixLen := len(OrderExpirationsPrefix)
	blockNumberBz := key[prefixLen : prefixLen+Uint64BytesLen]
	marketIDBz := key[prefixLen+Uint64BytesLen:]
	
	return int64(sdk.BigEndianToUint64(blockNumberBz)), common.BytesToHash(marketIDBz)
}
.golangci.yml (1)

58-64: Consider documenting reasons for path exclusions

The extensive list of excluded paths might make it difficult to know which parts of the codebase are not being linted. Consider adding comments to explain why these directories are excluded to help future maintainers understand the decisions.

    paths:
-      - chain
-      - exchange
-      - injective_data
-      - proto
-      - client/keyring/testdata
-      - client/chain/chain_test_support.go
+      - chain        # Excluded because: [add reason here]
+      - exchange     # Excluded because: [add reason here] 
+      - injective_data # Excluded because: [add reason here]
+      - proto        # Generated code
+      - client/keyring/testdata # Test data
+      - client/chain/chain_test_support.go # Test support code
chain/evm/types/logs.go (2)

30-47: Validate rejects pending-transaction logs

A block height of 0 is legitimate for pending logs, yet line 62 treats it as an error. This prevents relaying unconfirmed txs through the API.

Consider either:

  1. Allowing BlockNumber == 0 when Removed == false, or
  2. Removing the check entirely and relying on upstream validation.

91-100: Return an empty slice instead of nil for zero logs

NewLogsFromEth returns nil when the input slice is empty. Callers that range over the result or append to it will have to add an extra nil-check. Returning an empty slice avoids this foot-gun and follows Go conventions.

-	if len(ethlogs) == 0 {
-		return nil
-	}
-	logs := make([]*Log, 0, len(ethlogs))
+	logs := make([]*Log, 0, len(ethlogs))
+	if len(ethlogs) == 0 {
+		return logs
+	}
chain/exchange/types/common_utils.go (1)

311-342: Duplicate-detection helpers can be consolidated with generics

Three almost-identical functions repeat the same pattern. Go 1.18 generics allow a single, type-agnostic helper, reducing code surface and maintenance.

// HasDuplicates reports whether f(x) produces the same key for any two elements.
func HasDuplicates[T any, K comparable](slice []T, key func(T) K) bool {
	seen := make(map[K]struct{})
	for _, item := range slice {
		k := key(item)
		if _, ok := seen[k]; ok {
			return true
		}
		seen[k] = struct{}{}
	}
	return false
}

// examples
_ = HasDuplicates[string, string](denoms, func(s string) string { return s })
_ = HasDuplicates[string, common.Hash](hashes, common.HexToHash)
_ = HasDuplicates[sdk.Coin, string](coins, func(c sdk.Coin) string { return c.Denom })

Migrating would remove ~40 lines while keeping behaviour intact.

chain/evm/types/chain_config.go (1)

96-110: Escaping local variables repeatedly is memory-inefficient

getTimeValue/getBlockValue create a local, take its address, and return it.
While Go’s escape analysis makes this safe, you repeatedly allocate identical *big.Int / uint64 values.

You can avoid the extra allocations:

-func getTimeValue(time *sdkmath.Int) *uint64 {
-    ...
-    t := time.BigIntMut().Uint64()
-    return &t
-}
+func getTimeValue(tInt *sdkmath.Int) *uint64 {
+    if tInt == nil || tInt.IsNegative() {
+        return nil
+    }
+    v := tInt.BigIntMut().Uint64()
+    return &v
+}

Same for getBlockValue.

chain/evm/types/eth.go (1)

39-48: Infinite recursion guard & nil handling in Unmarshal

tx.UnmarshalBinary(dst) is the method of the embedded *ethtypes.Transaction,
so recursion is fine, but tx.Transaction might still be nil when
len(dst)==0, forcing the caller to remember this edge case.

Consider normalising with:

if len(dst) == 0 {
    tx.Transaction = nil
    return nil
}
tx.Transaction = new(ethtypes.Transaction)
return tx.Transaction.UnmarshalBinary(dst)
chain/evm/types/tx_data.go (2)

78-98: DeriveChainID – slight readability & overflow improvement

The current implementation uses shadowed variables and manual subtraction.
A clearer, constant-free version prevents unsigned underflow:

func DeriveChainID(v *big.Int) *big.Int {
    if v == nil || v.Sign() <= 0 {
        return nil
    }
    vv := new(big.Int).Set(v)
    switch vv.Uint64() {
    case 27, 28:
        return new(big.Int) // zero => no EIP-155
    }

    // (v - 35) / 2  when v ≥ 35
    vv.Sub(vv, big.NewInt(35))
    return vv.Div(vv, big.NewInt(2))
}

Purely cosmetic but reduces cognitive load.


50-68: Factory ignores ethtypes.BlobTxType (EIP-4844)

Geth already defines BlobTxType (0x03). If/when the EVM module accepts those
transactions, the factory will misinterpret them as LegacyTx.

Consider future-proofing:

case ethtypes.BlobTxType:
    return nil, errorsmod.Wrap(ErrUnsupportedTxType, "EIP-4844 blob tx not supported yet")

A clear error is better than silent mis-parsing.

chain/exchange/types/authz_exchange_generic.go (1)

42-81: Consider a map instead of an exhaustive switch for URL()

The 30-line switch is repetitive and grows maintenance overhead each time a message is added/removed. A table-driven approach keeps the mapping declarative and reduces the risk of mismatched constants.

var msgTypeToURL = map[MsgType]string{
    MsgTypeDeposit:                          "/injective.exchange.v1beta1.MsgDeposit",
    MsgTypeWithdraw:                         "/injective.exchange.v1beta1.MsgWithdraw",
    // ...
}

func (m MsgType) URL() string {
    if url, ok := msgTypeToURL[m]; ok {
        return url
    }
    return "UNKNOWN"
}
chain/evm/types/params.go (2)

17-19: Comment contradicts constant value

DefaultAllowUnprotectedTxs is set to true, yet the comment says “rejects all unprotected txs (true = allow, false = reject)”. Either flip the boolean or adjust the wording.

-// DefaultAllowUnprotectedTxs rejects all unprotected txs (true = allow, false = reject)
+// DefaultAllowUnprotectedTxs controls acceptance of unprotected transactions (true = allow, false = reject)

60-75: Boolean validation adds little value

ValidateBool merely asserts the field is of type bool, which will always be true for a properly-generated struct. The extra indirection obscures the intent and bloats the Validate method.

Consider removing these checks unless the param types can actually vary at runtime.

chain/evm/types/key.go (1)

65-77: Validate indices before casting to uint64

ObjectGasUsedKey and ObjectBloomKey coerce intuint64 without bounds checking.
If a caller accidentally passes a negative txIndex/msgIndex, it will be converted to a huge positive uint64, corrupting the key-space silently.

Consider guarding the inputs:

-func ObjectGasUsedKey(txIndex int) []byte {
+func ObjectGasUsedKey(txIndex int) []byte {
+	if txIndex < 0 {
+		panic("negative txIndex in ObjectGasUsedKey")
+	}
 	…
 }
 
-func ObjectBloomKey(txIndex, msgIndex int) []byte {
+func ObjectBloomKey(txIndex, msgIndex int) []byte {
+	if txIndex < 0 || msgIndex < 0 {
+		panic("negative index in ObjectBloomKey")
+	}
 	…
 }

Alternatives: return an error instead of panicking, depending on how these helpers are used.

chain/evm/types/access_list_tx.go (1)

167-180: SetSignatureValues may lose the original ChainID on nil input

When chainID is nil the method leaves tx.ChainID unchanged, which is fine.
However, callers must remember to supply the value explicitly; otherwise, Validate() will later fail with “chain ID must be present”. Consider making the failure explicit here or documenting the contract in a comment to avoid confusion.

chain/evm/types/legacy_tx.go (1)

146-156: SetSignatureValues silently ignores chainID

Unlike AccessListTx, the legacy signer derives the chain-id from v, yet callers might still pass it in.
Because the parameter is discarded (_), consider:

  • Documenting this behaviour in a comment, or
  • Returning an error if a non-nil chainID is supplied to avoid accidental misuse.

Minor, but it helps future maintainers.

chain/evm/types/dynamic_fee_tx.go (1)

198-205: Minor wording & consistency nit

Error message strings miss the verb “be”:

-return errorsmod.Wrap(ErrInvalidGasCap, "gas tip cap cannot nil")
+return errorsmod.Wrap(ErrInvalidGasCap, "gas tip cap cannot be nil")

Apply the same fix for the gas-fee-cap branch below.

chain/evm/types/msg.go (1)

332-336: Builder type assertion may hide unsupported clients

When the assertion to authtx.ExtensionOptionsTxBuilder fails, the error text
is generic ("unsupported builder"). Consider including the concrete type
received to aid debugging:

return nil, fmt.Errorf("unsupported builder type %T", b)
chain/evm/types/errors.go (1)

238-254: NewVMErrorWithRet: nil cause path loses context

When abi.UnpackRevert fails, the function sets e.err but leaves
e.cause nil, so downstream ABCI logs lack module-level context.
Consider always setting cause, e.g.:

if errUnpack == nil {
    ...
-} else {
-    e.err = errors.New(e.vmErr)
-    e.cause = errorsmod.Wrap(ErrVMExecution, e.toJSON())
+} else {
+    e.err = errors.New(e.vmErr)
+    e.cause = errorsmod.Wrap(ErrVMExecution, e.toJSON())
 }
chain/evm/types/utils.go (2)

21-24: Spelling/grammar nit – “pratical” → “practical”.

Tiny typo in the comment can be fixed to keep docs polished.

-// Because priority is `int64` while price is `big.Int`, it's necessary to scale down the range to keep it more pratical.
+// Because priority is `int64` while price is `big.Int`, it's necessary to scale down the range to keep it more practical.

226-231: Guard against nil parameters in EffectiveGasPrice.

baseFee, feeCap, or tipCap being nil would currently panic inside math.BigMin / Add.
If this helper might ever be called with unset fields (e.g. during unit tests or partially-filled structs) add a short nil-check to return big.NewInt(0) or an error.

chain/exchange/types/market.go (1)

64-112: Validate decimals before exponentiation to avoid huge Dec allocations.

math.LegacyNewDec(10).Power(uint64(decimals)) can allocate very large numbers (and be slow) when an unexpected decimals > 18 sneaks in.
A cheap safeguard inside each converter avoids hidden panics:

+const maxSupportedDecimals = 18 // matches Cosmos-SDK Limit
+
 func PriceFromChainFormat(price math.LegacyDec, baseDecimals, quoteDecimals uint32) math.LegacyDec {
-    if price.IsNil() {
+    if price.IsNil() {
         return price
     }
+    if baseDecimals > maxSupportedDecimals || quoteDecimals > maxSupportedDecimals {
+        return math.LegacyZeroDec() // or panic / error – pick what suits the API
+    }

Apply the same bound check to Quantity* and Notional* helpers.

chain/exchange/types/msgs.go (3)

95-106: Interface naming & placement

UpdateSpotMarketMessage is handy, but living in this monolithic file makes discovery harder.
Consider extracting all update-market message helpers & shared validation into a small spot_update.go to improve locality.


113-122: ValidateAddress helper: return wrapped error for context.

Right now the original SDK error is lost once wrapped again at call-sites. Returning errorsmod.Wrap(err, ...) directly from the helper preserves stack/context and removes boiler-plate.

func ValidateAddress(address string) error {
-    _, err := sdk.AccAddressFromBech32(address)
-    return err
+    if _, err := sdk.AccAddressFromBech32(address); err != nil {
+        return errorsmod.Wrap(err, "invalid bech32 address")
+    }
+    return nil
}

1037-1064: Constructor overload explosion

Two almost-identical helpers create binary-option market orders. Instead of duplicating, expose one function that takes an optional scaleFactor (e.g. *uint32) or feed the factor directly (caller can pass market.OracleScaleFactor).

This removes one copy-pasted block and trims maintenance.

chain/exchange/types/derivative_orders.go (3)

13-28: Heavy code duplication for Cid() helpers

DerivativeOrder, DerivativeLimitOrder, and DerivativeMarketOrder all forward to OrderInfo.GetCid(). This repetition invites future drift and bloats the API surface.

Consider embedding a common interface (e.g. HasOrderInfo) or just use o.OrderInfo.GetCid() at call-sites.

-type DerivativeCID interface{ Cid() string }
-
-func (m *DerivativeLimitOrder) Cid() string   { return m.OrderInfo.GetCid() }
-func (o *DerivativeMarketOrder) Cid() string  { return o.OrderInfo.GetCid() }
-func (m *DerivativeOrder) Cid() string        { return m.OrderInfo.GetCid() }
+type derivativeWithInfo interface{ GetOrderInfo() *OrderInfo }
+
+func cid(of derivativeWithInfo) string { return of.GetOrderInfo().GetCid() }

29-39: Same duplication pattern for Price()

The three identical Price() wrappers suffer the same maintenance overhead noted above. Folding them behind one helper (or embedding OrderInfo in the proto) would reduce noise.


41-51: Same duplication pattern for SubaccountID()

Again, three copies of identical code. Refactor as suggested to keep the file lean.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77e624d and 0da105a.

⛔ Files ignored due to path filters (35)
  • chain/auction/types/query.pb.go is excluded by !**/*.pb.go
  • chain/erc20/types/erc20.pb.go is excluded by !**/*.pb.go
  • chain/erc20/types/events.pb.go is excluded by !**/*.pb.go
  • chain/erc20/types/genesis.pb.go is excluded by !**/*.pb.go
  • chain/erc20/types/params.pb.go is excluded by !**/*.pb.go
  • chain/erc20/types/query.pb.go is excluded by !**/*.pb.go
  • chain/erc20/types/tx.pb.go is excluded by !**/*.pb.go
  • chain/evm/types/access_tuple.pb.go is excluded by !**/*.pb.go
  • chain/evm/types/chain_config.pb.go is excluded by !**/*.pb.go
  • chain/evm/types/events.pb.go is excluded by !**/*.pb.go
  • chain/evm/types/genesis.pb.go is excluded by !**/*.pb.go
  • chain/evm/types/log.pb.go is excluded by !**/*.pb.go
  • chain/evm/types/params.pb.go is excluded by !**/*.pb.go
  • chain/evm/types/query.pb.go is excluded by !**/*.pb.go
  • chain/evm/types/state.pb.go is excluded by !**/*.pb.go
  • chain/evm/types/trace_config.pb.go is excluded by !**/*.pb.go
  • chain/evm/types/transaction_logs.pb.go is excluded by !**/*.pb.go
  • chain/evm/types/tx.pb.go is excluded by !**/*.pb.go
  • chain/evm/types/tx_result.pb.go is excluded by !**/*.pb.go
  • chain/exchange/types/authz.pb.go is excluded by !**/*.pb.go
  • chain/exchange/types/exchange.pb.go is excluded by !**/*.pb.go
  • chain/exchange/types/tx.pb.go is excluded by !**/*.pb.go
  • chain/exchange/types/v2/authz.pb.go is excluded by !**/*.pb.go
  • chain/exchange/types/v2/events.pb.go is excluded by !**/*.pb.go
  • chain/exchange/types/v2/exchange.pb.go is excluded by !**/*.pb.go
  • chain/exchange/types/v2/genesis.pb.go is excluded by !**/*.pb.go
  • chain/exchange/types/v2/market.pb.go is excluded by !**/*.pb.go
  • chain/exchange/types/v2/order.pb.go is excluded by !**/*.pb.go
  • chain/exchange/types/v2/orderbook.pb.go is excluded by !**/*.pb.go
  • chain/exchange/types/v2/proposal.pb.go is excluded by !**/*.pb.go
  • chain/exchange/types/v2/query.pb.go is excluded by !**/*.pb.go
  • chain/exchange/types/v2/tx.pb.go is excluded by !**/*.pb.go
  • chain/stream/types/v2/query.pb.go is excluded by !**/*.pb.go
  • chain/txfees/types/txfees.pb.go is excluded by !**/*.pb.go
  • chain/types/indexer.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (47)
  • .github/workflows/golangci-lint.yml (1 hunks)
  • .golangci.yml (1 hunks)
  • Makefile (4 hunks)
  • auth_vote/authz_vote.go (1 hunks)
  • chain/crypto/ethsecp256k1/ethsecp256k1.go (2 hunks)
  • chain/crypto/hd/algorithm.go (3 hunks)
  • chain/crypto/hd/hd_path.go (1 hunks)
  • chain/erc20/types/codec.go (1 hunks)
  • chain/evm/types/access_list.go (1 hunks)
  • chain/evm/types/access_list_tx.go (1 hunks)
  • chain/evm/types/chain_config.go (1 hunks)
  • chain/evm/types/codec.go (1 hunks)
  • chain/evm/types/dynamic_fee_tx.go (1 hunks)
  • chain/evm/types/errors.go (1 hunks)
  • chain/evm/types/eth.go (1 hunks)
  • chain/evm/types/events.go (1 hunks)
  • chain/evm/types/key.go (1 hunks)
  • chain/evm/types/legacy_tx.go (1 hunks)
  • chain/evm/types/logs.go (1 hunks)
  • chain/evm/types/msg.go (1 hunks)
  • chain/evm/types/params.go (1 hunks)
  • chain/evm/types/storage.go (1 hunks)
  • chain/evm/types/tx.go (1 hunks)
  • chain/evm/types/tx_data.go (1 hunks)
  • chain/evm/types/utils.go (1 hunks)
  • chain/exchange/types/authz_common.go (3 hunks)
  • chain/exchange/types/authz_derivative.go (5 hunks)
  • chain/exchange/types/authz_exchange_generic.go (1 hunks)
  • chain/exchange/types/authz_spot.go (5 hunks)
  • chain/exchange/types/codec.go (1 hunks)
  • chain/exchange/types/common_order.go (4 hunks)
  • chain/exchange/types/common_utils.go (2 hunks)
  • chain/exchange/types/deposit.go (0 hunks)
  • chain/exchange/types/derivative_orders.go (2 hunks)
  • chain/exchange/types/errors.go (3 hunks)
  • chain/exchange/types/exchange.go (1 hunks)
  • chain/exchange/types/fee_discounts.go (0 hunks)
  • chain/exchange/types/fee_validation.go (2 hunks)
  • chain/exchange/types/genesis.go (0 hunks)
  • chain/exchange/types/key.go (4 hunks)
  • chain/exchange/types/market.go (1 hunks)
  • chain/exchange/types/market_admin.go (1 hunks)
  • chain/exchange/types/msgs.go (14 hunks)
  • chain/exchange/types/msgs_validation.go (1 hunks)
  • chain/exchange/types/params.go (18 hunks)
  • chain/exchange/types/positions.go (1 hunks)
  • chain/exchange/types/proposal.go (3 hunks)
💤 Files with no reviewable changes (3)
  • chain/exchange/types/deposit.go
  • chain/exchange/types/genesis.go
  • chain/exchange/types/fee_discounts.go
🧰 Additional context used
🧬 Code Graph Analysis (21)
auth_vote/authz_vote.go (1)
client/common/network.go (1)
  • LoadNetwork (53-202)
chain/exchange/types/authz_spot.go (1)
chain/exchange/types/authz_common.go (1)
  • AuthorizedMarketsLimit (42-46)
chain/exchange/types/msgs_validation.go (4)
chain/exchange/types/msgs.go (1)
  • UpdateSpotMarketMessage (95-106)
chain/exchange/types/errors.go (7)
  • ErrInvalidAddress (91-91)
  • ErrMarketInvalid (13-13)
  • ErrBadField (12-12)
  • ErrInvalidTicker (19-19)
  • ErrInvalidPriceTickSize (42-42)
  • ErrInvalidQuantityTickSize (43-43)
  • ErrInvalidNotional (108-108)
chain/exchange/types/common_utils.go (1)
  • IsHexHash (110-120)
chain/exchange/types/params.go (3)
  • MaxTickerLength (40-40)
  • ValidateTickSize (370-410)
  • ValidateMinNotional (412-427)
chain/crypto/ethsecp256k1/ethsecp256k1.go (1)
chain/crypto/ethsecp256k1/keys.pb.go (6)
  • PrivKey (75-77)
  • PrivKey (81-81)
  • PrivKey (82-84)
  • PubKey (30-32)
  • PubKey (35-35)
  • PubKey (36-38)
chain/exchange/types/authz_derivative.go (1)
chain/exchange/types/authz_common.go (1)
  • AuthorizedMarketsLimit (42-46)
chain/evm/types/tx.go (2)
chain/evm/types/utils.go (1)
  • DefaultPriorityReduction (24-24)
chain/evm/types/errors.go (1)
  • ErrExecutionReverted (49-49)
chain/evm/types/events.go (1)
chain/evm/types/msg.go (1)
  • TypeMsgEthereumTx (40-40)
chain/exchange/types/common_order.go (1)
chain/exchange/types/exchange.pb.go (3)
  • OrderInfo (1185-1195)
  • OrderInfo (1199-1199)
  • OrderInfo (1200-1202)
chain/exchange/types/codec.go (1)
chain/exchange/types/authz.pb.go (3)
  • GenericExchangeAuthorization (615-621)
  • GenericExchangeAuthorization (625-625)
  • GenericExchangeAuthorization (626-628)
chain/exchange/types/fee_validation.go (1)
chain/exchange/types/exchange.pb.go (3)
  • FeeDiscountSchedule (2561-2572)
  • FeeDiscountSchedule (2576-2576)
  • FeeDiscountSchedule (2577-2579)
chain/exchange/types/params.go (2)
chain/exchange/types/paramset.go (1)
  • NewParamSetPair (16-18)
chain/exchange/types/exchange.pb.go (5)
  • AtomicMarketOrderAccessLevel (31-31)
  • AtomicMarketOrderAccessLevel (59-61)
  • Params (232-314)
  • Params (318-318)
  • Params (319-321)
chain/exchange/types/errors.go (1)
chain/exchange/types/key.go (1)
  • ModuleName (17-17)
chain/evm/types/chain_config.go (1)
chain/evm/types/errors.go (1)
  • ErrInvalidChainConfig (55-55)
chain/evm/types/logs.go (2)
chain/types/validation.go (1)
  • IsEmptyHash (12-14)
chain/evm/types/utils.go (1)
  • HexAddress (233-238)
chain/exchange/types/common_utils.go (6)
exchange/spot_exchange_rpc/pb/goadesign_goagen_injective_spot_exchange_rpc.pb.go (3)
  • Coin (4307-4316)
  • Coin (4331-4331)
  • Coin (4346-4348)
exchange/auction_rpc/pb/goadesign_goagen_injective_auction_rpc.pb.go (3)
  • Coin (226-235)
  • Coin (250-250)
  • Coin (265-267)
exchange/portfolio_rpc/pb/goadesign_goagen_injective_portfolio_rpc.pb.go (3)
  • Coin (379-388)
  • Coin (403-403)
  • Coin (418-420)
exchange/campaign_rpc/pb/goadesign_goagen_injective_campaign_rpc.pb.go (3)
  • Coin (370-379)
  • Coin (394-394)
  • Coin (409-411)
exchange/accounts_rpc/pb/goadesign_goagen_injective_accounts_rpc.pb.go (1)
  • Coin (1797-1806)
exchange/explorer_rpc/pb/goadesign_goagen_injective_explorer_rpc.pb.go (3)
  • Coin (6252-6261)
  • Coin (6276-6276)
  • Coin (6291-6293)
chain/evm/types/access_list_tx.go (4)
chain/types/int.go (2)
  • SafeNewIntFromBigInt (43-48)
  • IsValidInt256 (59-61)
chain/evm/types/access_list.go (2)
  • AccessList (10-10)
  • NewAccessList (14-34)
chain/evm/types/errors.go (1)
  • ErrInvalidGasFee (85-85)
typeddata/typed_data.go (1)
  • ErrInvalidChainID (1285-1285)
chain/exchange/types/exchange.go (2)
chain/exchange/types/exchange.pb.go (15)
  • AtomicMarketOrderAccessLevel (31-31)
  • AtomicMarketOrderAccessLevel (59-61)
  • AtomicMarketOrderAccessLevel_Nobody (34-34)
  • AtomicMarketOrderAccessLevel_SmartContractsOnly (37-37)
  • AtomicMarketOrderAccessLevel_BeginBlockerSmartContractsOnly (36-36)
  • AtomicMarketOrderAccessLevel_Everyone (38-38)
  • DerivativeOrder (1449-1460)
  • DerivativeOrder (1464-1464)
  • DerivativeOrder (1465-1467)
  • OrderInfo (1185-1195)
  • OrderInfo (1199-1199)
  • OrderInfo (1200-1202)
  • SpotOrder (1251-1260)
  • SpotOrder (1264-1264)
  • SpotOrder (1265-1267)
chain/exchange/types/common_utils.go (1)
  • Account (142-142)
chain/evm/types/params.go (1)
chain/evm/types/chain_config.go (1)
  • DefaultChainConfig (50-94)
chain/evm/types/dynamic_fee_tx.go (5)
chain/types/int.go (2)
  • SafeNewIntFromBigInt (43-48)
  • IsValidInt256 (59-61)
chain/evm/types/access_list.go (2)
  • AccessList (10-10)
  • NewAccessList (14-34)
chain/evm/types/errors.go (2)
  • ErrInvalidGasCap (97-97)
  • ErrInvalidGasFee (85-85)
typeddata/typed_data.go (1)
  • ErrInvalidChainID (1285-1285)
chain/evm/types/utils.go (1)
  • EffectiveGasPrice (228-230)
chain/evm/types/tx_data.go (4)
chain/evm/types/utils.go (1)
  • EffectiveGasPrice (228-230)
chain/evm/types/dynamic_fee_tx.go (1)
  • NewDynamicFeeTx (16-59)
chain/evm/types/access_list_tx.go (1)
  • NewAccessListTx (16-51)
chain/evm/types/legacy_tx.go (1)
  • NewLegacyTx (12-42)
chain/evm/types/utils.go (3)
chain/evm/types/tx.pb.go (6)
  • MsgEthereumTxResponse (302-319)
  • MsgEthereumTxResponse (323-323)
  • MsgEthereumTxResponse (324-326)
  • MsgEthereumTx (38-52)
  • MsgEthereumTx (56-56)
  • MsgEthereumTx (57-59)
chain/evm/types/events.go (2)
  • EventTypeTxLog (7-7)
  • AttributeKeyTxLog (16-16)
chain/evm/types/logs.go (1)
  • LogsToEthereum (103-109)
🔇 Additional comments (64)
auth_vote/authz_vote.go (1)

20-20: Leverage default websocket endpoint by removing explicit path
This change simplifies the Tendermint RPC client setup by relying on the library’s built-in default (/websocket) instead of hard-coding it. Please verify that the default path in cometbft/rpc/client/http matches your node’s configuration and doesn’t break any existing workflows.

chain/erc20/types/codec.go (1)

35-42: Double-registration on Authz Amino codec may be unnecessary

You already call RegisterCodec(ModuleCdc) which registers all concrete types on the module’s Amino codec.
Calling RegisterCodec(authzcdc.Amino) immediately afterwards repeats the registration on the global Authz codec. If that’s explicitly required for authz (e.g. to allow MsgExec.Msgs packing) keep it, otherwise it adds duplicate wiring and increases binary size.

Please confirm the necessity or drop it to keep the init-path minimal.

chain/exchange/types/authz_spot.go (1)

44-44: Great concurrency improvement: Using thread-safe accessor for AuthorizedMarketsLimit

The change from directly accessing the AuthorizedMarketsLimit constant to using the thread-safe accessor function AuthorizedMarketsLimit() improves concurrency safety across all authorization validation methods. This consistently applies the pattern established in authz_common.go where a mutex protects access to the limit value.

Also applies to: 84-84, 126-126, 166-166, 208-208

chain/crypto/ethsecp256k1/ethsecp256k1.go (1)

76-76: Code style improvement: Removed unused receiver names

Removing the named receiver variables (privKey and pubKey) from the Type() method declarations is a good stylistic improvement since these variables aren't used in the method bodies. This change makes the code more concise while maintaining the same functionality.

Also applies to: 156-156

chain/exchange/types/authz_derivative.go (1)

44-44: Great concurrency improvement: Using thread-safe accessor for AuthorizedMarketsLimit

The change from directly accessing the AuthorizedMarketsLimit constant to using the thread-safe accessor function AuthorizedMarketsLimit() improves concurrency safety across all derivative authorization validation methods. This consistently applies the same pattern used in the spot authorization types.

Also applies to: 84-84, 126-126, 166-166, 208-208

chain/exchange/types/authz_common.go (5)

5-5: Appropriate package import for concurrency support

Adding the sync package import is necessary to support the mutex-based concurrency control introduced in this file.


36-39: Good thread-safety implementation: Protecting shared state with mutex

Converting AuthorizedMarketsLimit from a constant to a protected variable with mutex synchronization is a good practice for state that may need to be modified at runtime or accessed concurrently.


41-53: Well-designed concurrency pattern: Thread-safe accessors

The implementation of AuthorizedMarketsLimit() and SetAuthorizedMarketsLimit(limit int) follows best practices for concurrent access:

  1. The getter uses a read lock which allows multiple concurrent readers
  2. The setter uses a write lock for exclusive access
  3. The getter uses defer to ensure the lock is always released

This pattern ensures that the limit can be safely accessed and modified from multiple goroutines.


132-132: Consistent usage of thread-safe accessor

Updating the ValidateBasic() method to use the new AuthorizedMarketsLimit() function instead of directly accessing the variable maintains consistency and ensures thread safety throughout the codebase.


120-120: Consider addressing the TODO comment

There's an unaddressed TODO comment about checking for "BO markets". Since this PR is adding Exchange V2 support, it might be a good opportunity to address this TODO if it's related to the V2 implementation.

Could you clarify if the TODO about checking "BO markets" is still relevant with Exchange V2, and if it should be addressed as part of this PR?

chain/crypto/hd/algorithm.go (4)

23-23: Documentation update to reflect Injective branding.

The comment is now correctly referencing "Injective" instead of "Ethermint", aligning with the project's branding.


27-27: Documentation update to reflect Injective branding.

Similar to the previous change, this comment now correctly references "Injective" instead of "Ethermint".


52-52: Stylistic improvement: Using unnamed receiver.

Changed from named receiver to unnamed receiver since the receiver variable isn't referenced within the method body.


97-97: Stylistic improvement: Using unnamed receiver.

Similar to the previous method, changed from named receiver to unnamed receiver for consistency.

chain/exchange/types/msgs_validation.go (2)

8-49: LGTM: Well-structured validation function with comprehensive checks.

The new validation function for UpdateSpotMarketMessage correctly:

  1. Validates the admin address format
  2. Ensures market ID is a valid hex hash
  3. Checks that at least one update field is provided
  4. Validates ticker length doesn't exceed the maximum allowed
  5. Properly validates tick sizes and minimum notional values when present

The error wrapping is clear and provides good context, using appropriate error codes from the errors module.


17-24: Good implementation of update validation logic.

This code elegantly ensures that at least one update field is specified. Without this check, a message with no actual updates could be processed, leading to unnecessary transactions.

Makefile (7)

4-4: Updated dependency to newer version.

Updating injective-indexer to v1.16.0-rc2 to support Exchange V2 functionality.


7-7: Updated dependency to newer version.

Updating injective-core to v1.16.0-beta.2 to align with the Exchange V2 implementation.


54-75: Added ERC20 and EVM types.

Expanded the copy-chain-types target to include the new ERC20 and EVM modules, which are necessary for Exchange V2 support. The EVM types particularly include comprehensive Ethereum transaction handling.


79-81: Added Exchange V2 types.

Now copying the Exchange V2 types, which is central to the PR objective of adding Exchange V2 support.


137-138: Added Stream V2 types.

Added support for Stream V2 protobuf files, necessary for the updated chain stream mentioned in the PR objectives.


142-144: Added additional utility types.

Added int.go, util.go, and validation.go to the chain types, which likely provide supporting functionality for Exchange V2.


200-202: Added comprehensive linting target.

The new lint-all target runs linting on the entire codebase without restricting to changed files, which is useful for ensuring overall code quality.

chain/exchange/types/codec.go (1)

169-170: Added registration for the new GenericExchangeAuthorization type.

This addition registers the new GenericExchangeAuthorization type as an implementation of the authz.Authorization interface, enabling generic authorization for exchange operations.

The implementation aligns with the PR objective of transitioning to Exchange V2 by providing more flexible authorization mechanisms.

chain/evm/types/tx.go (4)

11-29: Well-implemented transaction priority calculation with overflow protection.

The GetTxPriority function correctly calculates Ethereum transaction priority with proper handling of big integers and safety checks to prevent overflow. The use of DefaultPriorityReduction follows standard practices for scaling gas prices to priorities.


31-34: Simple and effective transaction failure check.

This implementation correctly determines if a transaction failed by checking the VM error string.


36-43: Good use of defensive copying to protect internal state.

The Return method appropriately uses common.CopyBytes to prevent mutation of internal state when returning transaction results, and correctly handles the failure case by returning nil.


45-52: Effective handling of revert data.

The Revert method properly extracts revert reason data only for REVERT operations, using defensive copying to protect internal state and correctly handling other error types.

chain/exchange/types/params.go (4)

71-71: Good export of constant for wider use.

Renaming minMarginRatio to MinMarginRatio makes this constant available to other packages that might need to reference the minimum margin ratio value.


105-148: Standardized validation function naming in parameter set pairs.

The consistent renaming of validation functions from validateXxx to ValidateXxx in the ParamSetPairs method improves API consistency and allows these validation functions to be used from other packages.


154-227: Standardized validation function calls in Validate method.

All validation function calls in the Validate method have been consistently updated to use the exported function names.


234-607: Comprehensive standardization of validation function exports.

All parameter validation functions have been consistently renamed from unexported (validateXxx) to exported (ValidateXxx) versions, maintaining the same validation logic while improving reusability across packages.

chain/evm/types/access_list.go (3)

8-10: Clear definition of AccessList type for EIP-2930 compatibility.

The AccessList type is properly defined as a slice of AccessTuple structs, enabling EIP-2930 access list functionality in the EVM module.


12-34: Well-implemented conversion from Ethereum core to protobuf-compatible AccessList.

The NewAccessList function correctly handles nil input, iterates through the Ethereum access list tuples, and performs appropriate conversions of addresses and storage keys to string format for protobuf compatibility.


36-55: Effective conversion from protobuf-compatible to Ethereum core AccessList.

The ToEthAccessList method properly converts the protobuf-compatible AccessList back to the Ethereum core format, handling address and storage key parsing appropriately.

chain/exchange/types/common_order.go (4)

10-10: Updated to more efficient protobuf implementation.

Switching from github.com/golang/protobuf/proto to github.com/gogo/protobuf/proto is a good modernization as it provides a more performant implementation of Protocol Buffers.


15-30: Simplified binary options order margin calculation.

Replacing the OrderType parameter with a boolean isBuy simplifies the function and makes it more straightforward to use. The conditional logic is now clearer and more direct.


83-89: Added getter methods for order information.

The new GetPrice() and GetQuantity() methods provide a consistent interface for accessing order information, which helps maintain encapsulation principles even though the fields are already public.


122-122: Exported order hash computation function.

Renaming computeOrderHash to ComputeOrderHash makes this utility function available to other packages, which improves reusability for testing and broader use cases.

chain/exchange/types/errors.go (1)

115-116: Verify new error codes are wired-in and covered by tests
ErrInvalidExpirationBlock (107) and ErrV1DerivativeMarketLaunch (108) are registered, but I could not find any usages in the diff.
• Make sure at least one code path returns each new error, otherwise the constants bloat the error table and future refactors may silently break.
• Add unit tests exercising the branches to lock the behaviour.

If the usages live outside the shown diff, feel free to ignore.

chain/exchange/types/fee_validation.go (1)

10-15: No functional change – 👍
Signature re-formatting improves readability without altering logic.

chain/crypto/hd/hd_path.go (1)

1-32: Well-structured implementation of HD path utilities for Ethereum

The implementation provides clean utilities for working with hierarchical deterministic (HD) wallet paths, specifically supporting both default and Ledger Live derivation paths. The code is well-organized with proper error handling and clear documentation.

chain/exchange/types/key.go (1)

342-345: Good fix for handling empty string edge case

The addition of the empty string check is a good improvement that handles the edge case when the string is empty after trimming, preventing potential parsing errors.

.golangci.yml (3)

1-4: Properly updated linter configuration version

The update to configuration version "2" ensures compatibility with newer versions of golangci-lint and improved configuration structure.


19-47: Comprehensive linter settings configuration

The detailed configuration for gocritic and revive linters provides good customization of linting rules, with appropriate thresholds for line length and cognitive complexity.


47-54: Well-structured exclusion configuration

The exclusions section uses presets effectively to avoid common false positives and unnecessary noise in the linting output.

chain/evm/types/storage.go (4)

11-14: Good design choice for non-deterministic storage representation

Using a slice of State pairs instead of a map is a good approach to prevent non-determinism during genesis initialization and export operations, which is essential for blockchain state consistency.


16-30: Well-implemented validation logic

The validation logic correctly checks for duplicate keys and validates each state entry, providing clear error messages that include the index of the problematic state entry.


60-66: Good constructor implementation

The NewState constructor properly creates a new State from common.Hash values, converting them to strings for storage. This simplifies the creation of State objects and ensures consistent formatting.


43-48:

✅ Verification successful

Consider implementing deep copy if State contains reference types

The current Copy method performs a shallow copy, which is appropriate for the current implementation since State doesn't contain reference types. However, if State is ever extended to include pointers or slices, this method should be updated to perform a deep copy.

Verify if the State type contains or will contain reference types:


🏁 Script executed:

#!/bin/bash
# Check State struct definition to see if it contains reference types
rg -A 10 "type State struct" chain/evm/types/

Length of output: 805


Shallow copy is sufficient for current State struct

Verified that chain/evm/types/state.pb.go defines State with only two string fields (Key and Value)—no pointers, slices, maps, or other reference types. The Copy method in chain/evm/types/storage.go therefore correctly duplicates all data as-is. No changes needed.

chain/evm/types/codec.go (1)

64-76: 🛠️ Refactor suggestion

Nil-safety missing in PackTxData

Calling PackTxData(nil) will dereference a nil interface and later panic when type-asserting. Add an explicit nil guard before the proto assertion.

 func PackTxData(txData TxData) (*codectypes.Any, error) {
+	if txData == nil {
+		return nil, errorsmod.Wrap(errortypes.ErrPackAny, "tx data cannot be nil")
+	}
 	msg, ok := txData.(proto.Message)

Likely an incorrect or invalid review comment.

chain/evm/types/logs.go (1)

55-69: Case-sensitive address validation may surprise users

Validate calls chaintypes.ValidateAddress, which usually enforces EIP-55 checksum casing. Down-stream tooling often emits lowercase addresses (e.g. geth when --nodiscover). Consider normalising to checksum or allowing lowercase to reduce friction.

chain/exchange/types/authz_exchange_generic.go (1)

118-135: Accept never signals exhaustion of the spend limit

After the loop, a.SpendLimit may be fully depleted, yet the AcceptResponse always sets Updated: &a and never toggles Delete.
Down-stream handlers will keep an “empty” authorization around, forcing every subsequent tx to execute the check even though no funds remain.

-	return authz.AcceptResponse{Accept: true, Updated: &a}, nil
+	if a.SpendLimit.IsZero() {
+		return authz.AcceptResponse{Accept: true, Delete: true}, nil
+	}
+	return authz.AcceptResponse{Accept: true, Updated: &a}, nil

Please confirm whether the SDK’s authz middleware already performs this clean-up automatically; if not, the patch above avoids stale grant entries.

chain/exchange/types/exchange.go (3)

32-41: Validation helper looks good

AtomicMarketOrderAccessLevel.IsValid exhaustively enumerates all protobuf variants and returns a sensible default. 👍


44-53: Unused structs may trigger dead-code/lint warnings

MatchedMarketDirection and TradingRewardAccountPoints were re-introduced, but no code in the current diff references them. If they’re truly vestigial, consider deleting them; if not, add usage or unit tests to avoid future pruning by linters.


55-64: GetFillable now aliases GetQuantity – is that intended?

Historically fillable often meant remaining quantity after partial fills. The new implementation just returns the original quantity, which could inflate matching logic elsewhere.

-func (m *DerivativeOrder) GetFillable() math.LegacyDec {
-	return m.GetQuantity()
-}
+// If partial execution is possible, compute outstanding quantity instead.
+func (m *DerivativeOrder) GetFillable() math.LegacyDec {
+	// TODO: subtract executed amount once that state is tracked.
+	return m.OrderInfo.Quantity
+}
chain/evm/types/access_list_tx.go (1)

183-224: Validation misses zero-value edge cases & re-uses expensive math checks

  1. A gas price of 0 is technically allowed on Ethereum only for EIP-1559 base-fee burning; for AccessListTx (EIP-2930) a zero gas price would make the tx invalid on main-net. If the SDK mirrors that rule, add an explicit gasPrice.Sign() == 0 check.
  2. IsValidInt256(tx.Fee()) recomputes the fee big-int; this is inexpensive but can be saved by calling Fee() once and re-using the value (you already need it for the overflow check).
  3. Consider adding an upper bound check on GasLimit to prevent DoS-style gigantic values.

Not blocking, but tightening these guards improves safety.

chain/evm/types/legacy_tx.go (1)

159-192: Extend validation to cover gas-limit and zero gas-price corner cases

As with AccessListTx, the validation logic could be hardened:

  • Reject a GasPrice of 0 – legacy transactions are invalid with zero gas price.
  • Enforce a sensible upper bound for GasLimit (e.g. 0 < gasLimit ≤ 30_000_000).
  • Confirm that Fee() will not overflow 256-bits after multiplication.

Adding these guards reduces the chance of malformed txs entering state.

chain/exchange/types/proposal.go (1)

792-794: 👍 Correct margin-ratio relation check.

Requiring initialMarginRatio > maintenanceMarginRatio prevents nonsensical or instantly-liquidatable markets. Looks good.

Also applies to: 887-889

chain/exchange/types/msgs.go (4)

238-242: Margin ordering check-– LGTM

Switch to <= guarantees initial > maintenance. Simple and correct.


345-396: Is zero‐margin intentionally allowed?

m.Margin.IsNil() || m.Margin.LT(0) now accepts 0 as valid. For non-reduce-only derivative orders, zero margin usually makes the order unfillable. Confirm business rule; if zero margin should be rejected change to LTE.


1395-1410: Good catch – correct sender / sub-account match logic

Comparing sourceSubaccountID against senderAddr fixes previous false positives. ✔️


2013-2029: 🛠️ Refactor suggestion

Edge case: duplicate detection treats “empty hash” as unique key

hasDuplicatesOrder stores every hash and cid including the empty string.
Although a later ValidateBasic should prevent both being empty, the duplicate-checking occurs before per-item validation in MsgBatchUpdateOrders.ValidateBasic. Two invalid records with both fields blank would thus be flagged as duplicates rather than “missing identifier”, which may hide the real error source.

Proposal: run hasDuplicatesOrder after each OrderData.ValidateBasic succeeds, or ignore entries when both hash and cid are empty.

if hash != "" {
    if _, ok := seenHashes[hash]; ok {
        return true
    }
    seenHashes[hash] = struct{}{}
}
if cid != "" {
    if _, ok := seenCids[cid]; ok {
        return true
    }
    seenCids[cid] = struct{}{}
}

Likely an incorrect or invalid review comment.

chain/exchange/types/derivative_orders.go (1)

53-55: Inconsistent camel-case field name may hide bug

SubaccountId (lower-case “d”) is used here, whereas surrounding Go code often follows SubaccountID. If protobuf regenerates with SubaccountID, this accessor will compile but return the zero hash, silently breaking logic.

Please double-check the generated struct tags and align the field name.

Comment on lines +7 to 11
type PositionPayout struct {
Payout math.LegacyDec
PnlNotional math.LegacyDec
IsProfitable bool
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Exported struct introduced without comment and all business logic removed

  1. PositionPayout is now exported but lacks a doc-comment → will fail lint.
  2. All position-handling logic previously in this file has been deleted.
    Unless that code was migrated elsewhere, callers referencing those helpers (ApplyFunding, ClosePosition, etc.) will fail to compile.
-type PositionPayout struct {
-    Payout       math.LegacyDec
-    PnlNotional  math.LegacyDec
-    IsProfitable bool
-}
+// PositionPayout captures the settlement outcome for a derivative position.
+// TODO: update references after the V2 refactor.
+type PositionPayout struct {
+    Payout       math.LegacyDec
+    PnlNotional  math.LegacyDec
+    IsProfitable bool
+}

Please verify that:
• All removed methods were intentionally relocated.
• No downstream packages still import them.

A quick compile-test will reveal breakages.


🏁 Script executed:

#!/bin/bash
set -e

echo "Looking for ApplyFunding definitions..."
rg "func ApplyFunding" -A2 || echo "❌ No ApplyFunding definition found"

echo "Looking for ClosePosition definitions..."
rg "func ClosePosition" -A2 || echo "❌ No ClosePosition definition found"

echo "Looking for calls to ApplyFunding..."
rg "ApplyFunding\(" -C2 || echo "❌ No ApplyFunding calls found"

echo "Looking for calls to ClosePosition..."
rg "ClosePosition\(" -C2 || echo "❌ No ClosePosition calls found"

echo "Looking for PositionPayout references across the repo..."
rg "PositionPayout" -C2 || echo "❌ No PositionPayout references outside this file"

Length of output: 3162


Add a doc-comment for the exported PositionPayout

The PositionPayout struct in
chain/exchange/types/positions.go is now exported and consumed by the V2 code
(chain/exchange/types/v2/position.go → GetPayoutIfFullyClosing), but it lacks a
top-of-type comment (lint will error).

• File: chain/exchange/types/positions.go (around line 7)
• Suggested comment:

// PositionPayout captures the settlement outcome for a derivative position,
// including the realized payout amount, P&L notional, and whether it was profitable.

All position-handling logic (ApplyFunding, ClosePosition, etc.) has been moved
to chain/exchange/types/v2/position.go, so no downstream breakages are expected.

🤖 Prompt for AI Agents
In chain/exchange/types/positions.go around lines 7 to 11, add a doc-comment
above the exported PositionPayout struct to satisfy lint requirements. Use the
suggested comment: "PositionPayout captures the settlement outcome for a
derivative position, including the realized payout amount, P&L notional, and
whether it was profitable." Confirm that all removed position-handling logic has
been relocated to chain/exchange/types/v2/position.go to avoid breaking
downstream code.

Comment on lines +22 to 26
uses: golangci/golangci-lint-action@v8
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: latest
version: v2.1.6

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

version: v2.1.6 is invalid for golangci-lint

golangci-lint releases are still on the v1.x series (latest ≈ v1.55.x).
Setting version: v2.1.6 causes the action to 404 and the workflow to fail.

-          version: v2.1.6
+#         version: v1.55.2   # ← pin to an existing release
+          version: latest     # or drop the line to track the default

Please adjust the version or omit the key entirely.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uses: golangci/golangci-lint-action@v8
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: latest
version: v2.1.6
uses: golangci/golangci-lint-action@v8
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
# version: v1.55.2 # ← pin to an existing release
version: latest # or drop the line to track the default
🤖 Prompt for AI Agents
In .github/workflows/golangci-lint.yml around lines 22 to 26, the golangci-lint
version is set to v2.1.6, which is invalid because golangci-lint releases are
still in the v1.x series. To fix this, change the version to a valid v1.x
version like v1.55.2 or remove the version key entirely to use the default
latest v1.x release.

Comment on lines +80 to +90
func UnpackTxData(any *codectypes.Any) (TxData, error) {
if any == nil {
return nil, errorsmod.Wrap(errortypes.ErrUnpackAny, "protobuf Any message cannot be nil")
}

txData, ok := any.GetCachedValue().(TxData)
if !ok {
return nil, errorsmod.Wrapf(errortypes.ErrUnpackAny, "cannot unpack Any into TxData %T", any)
}

return txData, nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use UnpackAny helper to avoid empty cached values

any.GetCachedValue() returns nil unless the Any was previously unpacked, therefore the current check will falsely fail for freshly-received messages. Prefer codectypes.UnpackAny which fills the cache and performs type checking.

-	txData, ok := any.GetCachedValue().(TxData)
-	if !ok {
-		return nil, errorsmod.Wrapf(errortypes.ErrUnpackAny, "cannot unpack Any into TxData %T", any)
-	}
-	return txData, nil
+	var txData TxData
+	if err := codectypes.UnpackAny(&txData, any); err != nil {
+		return nil, errorsmod.Wrap(errortypes.ErrUnpackAny, err.Error())
+	}
+	return txData, nil
🤖 Prompt for AI Agents
In chain/evm/types/codec.go around lines 80 to 90, the code uses
any.GetCachedValue() which returns nil for freshly-received protobuf Any
messages, causing false unpack failures. Replace the use of any.GetCachedValue()
with the codectypes.UnpackAny helper function to properly unpack the Any
message, fill the cache, and perform type checking. Adjust the function to call
codectypes.UnpackAny with the Any message and a pointer to a TxData variable,
then return the unpacked TxData or an error accordingly.

Comment on lines +37 to +49
registry.RegisterImplementations(
(*sdk.Msg)(nil),
&MsgEthereumTx{},
)
registry.RegisterImplementations(
(*tx.TxExtensionOptionI)(nil),
&ExtensionOptionsEthereumTx{},
)
registry.RegisterImplementations(
(*sdk.Msg)(nil),
&MsgEthereumTx{},
&MsgUpdateParams{},
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Duplicate MsgEthereumTx registration can lead to runtime panics

MsgEthereumTx is registered twice for the same sdk.Msg interface (lines 37-40 and again at 46-49).
codectypes.InterfaceRegistry.RegisterImplementations panics on duplicate registrations, so this can crash the node at start-up.

-	registry.RegisterImplementations(
-		(*sdk.Msg)(nil),
-		&MsgEthereumTx{},
-	)
...
-	registry.RegisterImplementations(
-		(*sdk.Msg)(nil),
-		&MsgEthereumTx{},
-		&MsgUpdateParams{},
-	)
+	registry.RegisterImplementations(
+		(*sdk.Msg)(nil),
+		&MsgEthereumTx{},
+		&MsgUpdateParams{},
+	)
🤖 Prompt for AI Agents
In chain/evm/types/codec.go around lines 37 to 49, the MsgEthereumTx type is
registered twice for the sdk.Msg interface, which causes a runtime panic due to
duplicate registrations. To fix this, remove the redundant registration of
MsgEthereumTx in the second RegisterImplementations call and ensure each type is
registered only once per interface to prevent startup crashes.

Comment on lines +178 to +184
func ValidateHash(hex string) error {
if hex != "" && strings.TrimSpace(hex) == "" {
return errorsmod.Wrap(ErrInvalidChainConfig, "hash cannot be blank")
}

return nil
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

ValidateHash allows whitespace and malformed hashes

Currently the only invalid case is a non-empty all-whitespace string. This passes:

"EIP150Hash": "not-a-hex"

Strengthen validation (see comment above) to protect against configuration mistakes.

🤖 Prompt for AI Agents
In chain/evm/types/chain_config.go around lines 178 to 184, the ValidateHash
function only rejects non-empty strings that are all whitespace but allows
malformed hashes like "not-a-hex". Update the function to check that the input
string is a valid hex-encoded hash of the expected length and format, returning
an error if it is not. This will prevent invalid or malformed hashes from
passing validation and protect against configuration mistakes.

Comment on lines +160 to 172
func (m *DerivativeMarketOrder) FillableQuantity() math.LegacyDec {
return m.OrderInfo.Quantity
}

func (m *DerivativeMarketOrder) Price() math.LegacyDec {
return m.OrderInfo.Price
}

func (m *DerivativeLimitOrder) Price() math.LegacyDec {
return m.OrderInfo.Price
}

func (m *DerivativeOrder) Price() math.LegacyDec {
return m.OrderInfo.Price
}

func (o *DerivativeOrder) IsConditional() bool {
return o.OrderType.IsConditional()
}

func (o *DerivativeMarketOrder) IsConditional() bool {
return o.OrderType.IsConditional()
}

func (o *DerivativeLimitOrder) IsConditional() bool {
return o.OrderType.IsConditional()
}

func (o *DerivativeLimitOrder) Cid() string {
return o.OrderInfo.GetCid()
}

func (o *DerivativeMarketOrder) Cid() string {
return o.OrderInfo.GetCid()
}

func (o *DerivativeOrder) SubaccountID() common.Hash {
return o.OrderInfo.SubaccountID()
}

func (o *DerivativeOrder) Cid() string {
return o.OrderInfo.GetCid()
}

func (o *DerivativeOrder) IsFromDefaultSubaccount() bool {
return o.OrderInfo.IsFromDefaultSubaccount()
}

func (o *DerivativeOrder) MarketID() common.Hash {
return common.HexToHash(o.MarketId)
}

func (o *DerivativeMarketOrder) SubaccountID() common.Hash {
return o.OrderInfo.SubaccountID()
}

func (o *DerivativeLimitOrder) SubaccountID() common.Hash {
return o.OrderInfo.SubaccountID()
}

func (o *OrderInfo) SubaccountID() common.Hash {
return common.HexToHash(o.SubaccountId)
}

func (o *OrderInfo) FeeRecipientAddress() common.Address {
address, _ := sdk.AccAddressFromBech32(o.FeeRecipient)
return common.BytesToAddress(address.Bytes())
}

func (o *DerivativeLimitOrder) SdkAccAddress() sdk.AccAddress {
return sdk.AccAddress(o.SubaccountID().Bytes()[:common.AddressLength])
}

func (o *DerivativeLimitOrder) IsFromDefaultSubaccount() bool {
return o.OrderInfo.IsFromDefaultSubaccount()
}

func (o *DerivativeMarketOrder) SdkAccAddress() sdk.AccAddress {
return sdk.AccAddress(o.SubaccountID().Bytes()[:common.AddressLength])
}

func (o *DerivativeMarketOrder) IsFromDefaultSubaccount() bool {
return o.OrderInfo.IsFromDefaultSubaccount()
}

func (o *TrimmedDerivativeLimitOrder) IsReduceOnly() bool {
return o.Margin.IsZero()
}

func EmptyDerivativeMarketOrderResults() *DerivativeMarketOrderResults {
return &DerivativeMarketOrderResults{
Quantity: math.LegacyZeroDec(),
Price: math.LegacyZeroDec(),
Fee: math.LegacyZeroDec(),
PositionDelta: PositionDelta{},
Payout: math.LegacyZeroDec(),
}
o.SetQuantity(newQuantity)
return nil
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

ResizeReduceOnlyOrder silently allows quantity increase

If newQuantity > oldQuantity, quantityDecrement is negative, the function returns nil, and the caller may proceed with an enlarged reduce-only order – violating the definition of “reduce only”.

Either:

  1. Explicitly reject increases:
if !quantityDecrement.IsPositive() {
-   return nil
+   return ErrOrderInvalid.Wrap("new quantity must be smaller than current for reduce-only resize")
}
  1. Or rename the helper to reflect that it is a no-op on increases.

Also consider adding an IsNegative() guard for newQuantity itself.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func ResizeReduceOnlyOrder(o IMutableDerivativeOrder, newQuantity math.LegacyDec) error {
if o.IsVanilla() {
return ErrOrderInvalid.Wrap("ResizeReduceOnlyOrder should only be used for reduce only orders!")
}
return computeOrderHash(marketId, o.OrderInfo.SubaccountId, o.OrderInfo.FeeRecipient, o.OrderInfo.Price.String(), o.OrderInfo.Quantity.String(), o.Margin.String(), triggerPrice, string(o.OrderType), nonce)
}
// ComputeOrderHash computes the order hash for given derivative limit order
func (o *DerivativeOrder) ComputeOrderHash(nonce uint32) (common.Hash, error) {
triggerPrice := ""
if o.TriggerPrice != nil {
triggerPrice = o.TriggerPrice.String()
quantityDecrement := o.GetQuantity().Sub(newQuantity)
if !quantityDecrement.IsPositive() {
return nil
}
return computeOrderHash(o.MarketId, o.OrderInfo.SubaccountId, o.OrderInfo.FeeRecipient, o.OrderInfo.Price.String(), o.OrderInfo.Quantity.String(), o.Margin.String(), triggerPrice, string(o.OrderType), nonce)
}
func (o *DerivativeOrder) IsReduceOnly() bool {
return o.Margin.IsZero()
}
func (o *DerivativeMarketOrder) IsReduceOnly() bool {
return o.Margin.IsZero()
}
func (o *DerivativeLimitOrder) IsReduceOnly() bool {
return o.Margin.IsZero()
}
func (o *DerivativeLimitOrder) Hash() common.Hash {
return common.BytesToHash(o.OrderHash)
}
func (o *DerivativeMarketOrder) Hash() common.Hash {
return common.BytesToHash(o.OrderHash)
}
func (o *DerivativeLimitOrder) FeeRecipient() common.Address {
return o.OrderInfo.FeeRecipientAddress()
}
func (o *DerivativeMarketOrder) FeeRecipient() common.Address {
return o.OrderInfo.FeeRecipientAddress()
}
func (o *DerivativeOrder) IsVanilla() bool {
return !o.IsReduceOnly()
}
func (o *DerivativeMarketOrder) IsVanilla() bool {
return !o.IsReduceOnly()
}
func (o *DerivativeLimitOrder) IsVanilla() bool {
return !o.IsReduceOnly()
}
func (m *DerivativeMarketOrder) IsBuy() bool {
return m.OrderType.IsBuy()
}
func (m *DerivativeLimitOrder) IsBuy() bool {
return m.OrderType.IsBuy()
}
func (m *DerivativeOrder) IsBuy() bool {
return m.OrderType.IsBuy()
}
func (m *DerivativeMarketOrder) Quantity() math.LegacyDec {
return m.OrderInfo.Quantity
}
func (m *DerivativeMarketOrder) FillableQuantity() math.LegacyDec {
return m.OrderInfo.Quantity
}
func (m *DerivativeMarketOrder) Price() math.LegacyDec {
return m.OrderInfo.Price
}
func (m *DerivativeLimitOrder) Price() math.LegacyDec {
return m.OrderInfo.Price
}
func (m *DerivativeOrder) Price() math.LegacyDec {
return m.OrderInfo.Price
}
func (o *DerivativeOrder) IsConditional() bool {
return o.OrderType.IsConditional()
}
func (o *DerivativeMarketOrder) IsConditional() bool {
return o.OrderType.IsConditional()
}
func (o *DerivativeLimitOrder) IsConditional() bool {
return o.OrderType.IsConditional()
}
func (o *DerivativeLimitOrder) Cid() string {
return o.OrderInfo.GetCid()
}
func (o *DerivativeMarketOrder) Cid() string {
return o.OrderInfo.GetCid()
}
func (o *DerivativeOrder) SubaccountID() common.Hash {
return o.OrderInfo.SubaccountID()
}
func (o *DerivativeOrder) Cid() string {
return o.OrderInfo.GetCid()
}
func (o *DerivativeOrder) IsFromDefaultSubaccount() bool {
return o.OrderInfo.IsFromDefaultSubaccount()
}
func (o *DerivativeOrder) MarketID() common.Hash {
return common.HexToHash(o.MarketId)
}
func (o *DerivativeMarketOrder) SubaccountID() common.Hash {
return o.OrderInfo.SubaccountID()
}
func (o *DerivativeLimitOrder) SubaccountID() common.Hash {
return o.OrderInfo.SubaccountID()
}
func (o *OrderInfo) SubaccountID() common.Hash {
return common.HexToHash(o.SubaccountId)
}
func (o *OrderInfo) FeeRecipientAddress() common.Address {
address, _ := sdk.AccAddressFromBech32(o.FeeRecipient)
return common.BytesToAddress(address.Bytes())
}
func (o *DerivativeLimitOrder) SdkAccAddress() sdk.AccAddress {
return sdk.AccAddress(o.SubaccountID().Bytes()[:common.AddressLength])
}
func (o *DerivativeLimitOrder) IsFromDefaultSubaccount() bool {
return o.OrderInfo.IsFromDefaultSubaccount()
}
func (o *DerivativeMarketOrder) SdkAccAddress() sdk.AccAddress {
return sdk.AccAddress(o.SubaccountID().Bytes()[:common.AddressLength])
}
func (o *DerivativeMarketOrder) IsFromDefaultSubaccount() bool {
return o.OrderInfo.IsFromDefaultSubaccount()
}
func (o *TrimmedDerivativeLimitOrder) IsReduceOnly() bool {
return o.Margin.IsZero()
}
func EmptyDerivativeMarketOrderResults() *DerivativeMarketOrderResults {
return &DerivativeMarketOrderResults{
Quantity: math.LegacyZeroDec(),
Price: math.LegacyZeroDec(),
Fee: math.LegacyZeroDec(),
PositionDelta: PositionDelta{},
Payout: math.LegacyZeroDec(),
}
o.SetQuantity(newQuantity)
return nil
}
func ResizeReduceOnlyOrder(o IMutableDerivativeOrder, newQuantity math.LegacyDec) error {
if o.IsVanilla() {
return ErrOrderInvalid.Wrap("ResizeReduceOnlyOrder should only be used for reduce only orders!")
}
quantityDecrement := o.GetQuantity().Sub(newQuantity)
if !quantityDecrement.IsPositive() {
return ErrOrderInvalid.Wrap("new quantity must be smaller than current for reduce-only resize")
}
o.SetQuantity(newQuantity)
return nil
}
🤖 Prompt for AI Agents
In chain/exchange/types/derivative_orders.go around lines 160 to 172, the
ResizeReduceOnlyOrder function currently allows the newQuantity to be greater
than the old quantity, which violates the reduce-only order constraint. To fix
this, add a check that explicitly rejects any increase in quantity by returning
an error if newQuantity is greater than the current quantity. Additionally, add
a guard to reject negative newQuantity values by returning an error if
newQuantity is negative. This ensures the function enforces the reduce-only
semantics correctly.

Comment on lines +118 to 138
// Test Code Only (for v1 tests)
func (m *DerivativeOrder) CheckInitialMarginRequirementMarkPriceThreshold(initialMarginRatio, markPrice math.LegacyDec) (err error) {
// For Buys: MarkPrice ≥ (Margin - Price * Quantity) / ((InitialMarginRatio - 1) * Quantity)
// For Sells: MarkPrice ≤ (Margin + Price * Quantity) / ((1 + InitialMarginRatio) * Quantity)
markPriceThreshold := o.ComputeInitialMarginRequirementMarkPriceThreshold(initialMarginRatio)
return CheckInitialMarginMarkPriceRequirement(o.IsBuy(), markPriceThreshold, markPrice)
markPriceThreshold := m.ComputeInitialMarginRequirementMarkPriceThreshold(initialMarginRatio)
return CheckInitialMarginMarkPriceRequirement(m.OrderType.IsBuy(), markPriceThreshold, markPrice)
}

// Test Code Only (for v1 tests)
func CheckInitialMarginMarkPriceRequirement(isBuyOrLong bool, markPriceThreshold, markPrice math.LegacyDec) error {
if isBuyOrLong && markPrice.LT(markPriceThreshold) {
return errors.Wrapf(ErrInsufficientMargin, "Buy MarkPriceThreshold Check: mark/trigger price %s must be GTE %s", markPrice.String(), markPriceThreshold.String())
return errors.Wrapf(
ErrInsufficientMargin,
"Buy MarkPriceThreshold Check: mark/trigger price %s must be GTE %s", markPrice.String(), markPriceThreshold.String())
} else if !isBuyOrLong && markPrice.GT(markPriceThreshold) {
return errors.Wrapf(ErrInsufficientMargin, "Sell MarkPriceThreshold Check: mark/trigger price %s must be LTE %s", markPrice.String(), markPriceThreshold.String())
}
return nil
}

// CheckValidConditionalPrice checks that conditional order type (STOP or TAKE) actually valid for current relation between triggerPrice and markPrice
func (o *DerivativeOrder) CheckValidConditionalPrice(markPrice math.LegacyDec) (err error) {
if !o.IsConditional() {
return nil
}

ok := true
switch o.OrderType {
case OrderType_STOP_BUY, OrderType_TAKE_SELL: // higher
ok = o.TriggerPrice.GT(markPrice)
case OrderType_STOP_SELL, OrderType_TAKE_BUY: // lower
ok = o.TriggerPrice.LT(markPrice)
}
if !ok {
return errors.Wrapf(ErrInvalidTriggerPrice, "order type %s incompatible with trigger price %s and markPrice %s", o.OrderType.String(), o.TriggerPrice.String(), markPrice.String())
return errors.Wrapf(
ErrInsufficientMargin,
"Sell MarkPriceThreshold Check: mark/trigger price %s must be LTE %s", markPrice.String(), markPriceThreshold.String())
}
return nil
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Denominator can be zero in initial-margin threshold check

For buy orders the denominator is (InitialMarginRatio − 1) * Quantity.
If initialMarginRatio == 1 the code divides by zero, panicking inside math.LegacyDec. Guard early and surface an explanatory error.

if isBuyOrLong {
-   denominator = initialMarginRatio.Sub(math.LegacyOneDec()).Mul(quantity)
+   diff := initialMarginRatio.Sub(math.LegacyOneDec())
+   if diff.IsZero() {
+       return math.LegacyZeroDec() // or return error up-stack
+   }
+   denominator = diff.Mul(quantity)
}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In chain/exchange/types/derivative_orders.go around lines 118 to 138, the
calculation for the initial margin threshold can cause a division by zero when
initialMarginRatio equals 1 for buy orders, as the denominator
(InitialMarginRatio - 1) * Quantity becomes zero. To fix this, add a guard
clause before performing the division in
ComputeInitialMarginRequirementMarkPriceThreshold or
CheckInitialMarginRequirementMarkPriceThreshold that checks if
initialMarginRatio equals 1 and return a clear error explaining the invalid
input instead of proceeding with the calculation.

Comment on lines +77 to 97
// Test Code Only (for v1 tests)
func (m *DerivativeOrder) ComputeOrderHash(nonce, scaleFactor uint32) (common.Hash, error) {
triggerPrice := ""
if m.TriggerPrice != nil {
triggerPrice = m.TriggerPrice.String()
}
// Margin = (scaled(1) - Price) * Quantity for sells
return o.OrderInfo.Quantity.Mul(GetScaledPrice(math.LegacyOneDec(), oracleScaleFactor).Sub(o.Price()))
}

func (o *DerivativeMarketOrder) GetRequiredBinaryOptionsMargin(oracleScaleFactor uint32) math.LegacyDec {
// Margin = Price * Quantity for buys
if o.IsBuy() {
notional := o.Price().Mul(o.OrderInfo.Quantity)
return notional
}
// Margin = (scaled(1) - Price) * Quantity for sells
return o.OrderInfo.Quantity.Mul(GetScaledPrice(math.LegacyOneDec(), oracleScaleFactor).Sub(o.Price()))
m.OrderInfo.Price = PriceFromChainFormat(m.OrderInfo.Price, 0, scaleFactor)
m.Margin = NotionalFromChainFormat(m.Margin, scaleFactor)

return ComputeOrderHash(
m.MarketId,
m.OrderInfo.SubaccountId,
m.OrderInfo.FeeRecipient,
m.OrderInfo.Price.String(),
m.OrderInfo.Quantity.String(),
m.Margin.String(),
triggerPrice,
string(m.OrderType),
nonce)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

DerivativeOrder.ComputeOrderHash mutates receiver – surprising side-effect

The method rewrites m.OrderInfo.Price and m.Margin via PriceFromChainFormat / NotionalFromChainFormat. When test code calls the method twice, the 2nd hash will be different, and callers might not expect the order object to be converted in-place.

If mutation is unnecessary, make defensive copies:

-price := PriceFromChainFormat(m.OrderInfo.Price, 0, scaleFactor)
-margin := NotionalFromChainFormat(m.Margin, scaleFactor)
-
-m.OrderInfo.Price = price
-m.Margin = margin
+price  := PriceFromChainFormat(m.OrderInfo.Price, 0, scaleFactor)
+margin := NotionalFromChainFormat(m.Margin, scaleFactor)

and pass the local variables to ComputeOrderHash.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In chain/exchange/types/derivative_orders.go lines 77 to 97, the
ComputeOrderHash method mutates the receiver by modifying m.OrderInfo.Price and
m.Margin in place, causing unexpected side effects on repeated calls. To fix
this, avoid mutating the receiver by creating local copies of Price and Margin
converted via PriceFromChainFormat and NotionalFromChainFormat, then pass these
local variables to ComputeOrderHash instead of modifying the fields on m
directly.

Comment on lines +107 to 116
// Test Code Only (for v1 tests)
func (m *DerivativeLimitOrder) GetCancelRefundAmount(feeRate math.LegacyDec) math.LegacyDec {
marginHoldRefund := math.LegacyZeroDec()
if o.IsVanilla() {
// negative fees are only accounted for upon matching
if !m.Margin.IsZero() {
positiveFeePart := math.LegacyMaxDec(math.LegacyZeroDec(), feeRate)
//nolint:all
// Refund = (FillableQuantity / Quantity) * (Margin + Price * Quantity * feeRate)
notional := o.OrderInfo.Price.Mul(o.OrderInfo.Quantity)
marginHoldRefund = o.Fillable.Mul(o.Margin.Add(notional.Mul(positiveFeePart))).Quo(o.OrderInfo.Quantity)
notional := m.OrderInfo.Price.Mul(m.OrderInfo.Quantity)
marginHoldRefund = m.Fillable.Mul(m.Margin.Add(notional.Mul(positiveFeePart))).Quo(m.OrderInfo.Quantity)
}
return marginHoldRefund
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Division-by-zero possibility when Quantity is zero

GetCancelRefundAmount divides by m.OrderInfo.Quantity without guarding against a zero quantity limit-order. A malformed test fixture will panic.

+if m.OrderInfo.Quantity.IsZero() {
+	return math.LegacyZeroDec()
+}
marginHoldRefund = m.Fillable.Mul(...).Quo(m.OrderInfo.Quantity)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Test Code Only (for v1 tests)
func (m *DerivativeLimitOrder) GetCancelRefundAmount(feeRate math.LegacyDec) math.LegacyDec {
marginHoldRefund := math.LegacyZeroDec()
if o.IsVanilla() {
// negative fees are only accounted for upon matching
if !m.Margin.IsZero() {
positiveFeePart := math.LegacyMaxDec(math.LegacyZeroDec(), feeRate)
//nolint:all
// Refund = (FillableQuantity / Quantity) * (Margin + Price * Quantity * feeRate)
notional := o.OrderInfo.Price.Mul(o.OrderInfo.Quantity)
marginHoldRefund = o.Fillable.Mul(o.Margin.Add(notional.Mul(positiveFeePart))).Quo(o.OrderInfo.Quantity)
notional := m.OrderInfo.Price.Mul(m.OrderInfo.Quantity)
marginHoldRefund = m.Fillable.Mul(m.Margin.Add(notional.Mul(positiveFeePart))).Quo(m.OrderInfo.Quantity)
}
return marginHoldRefund
}
// Test Code Only (for v1 tests)
func (m *DerivativeLimitOrder) GetCancelRefundAmount(feeRate math.LegacyDec) math.LegacyDec {
// Guard against division by zero
if m.OrderInfo.Quantity.IsZero() {
return math.LegacyZeroDec()
}
marginHoldRefund := math.LegacyZeroDec()
if !m.Margin.IsZero() {
positiveFeePart := math.LegacyMaxDec(math.LegacyZeroDec(), feeRate)
notional := m.OrderInfo.Price.Mul(m.OrderInfo.Quantity)
marginHoldRefund = m.Fillable.
Mul(m.Margin.Add(notional.Mul(positiveFeePart))).
Quo(m.OrderInfo.Quantity)
}
return marginHoldRefund
}
🤖 Prompt for AI Agents
In chain/exchange/types/derivative_orders.go around lines 107 to 116, the method
GetCancelRefundAmount divides by m.OrderInfo.Quantity without checking if it is
zero, which can cause a division-by-zero panic. Add a guard clause to return
zero or handle the case when m.OrderInfo.Quantity is zero before performing the
division to prevent runtime panics.

Comment on lines +9 to 11
func GetScaledPrice(price math.LegacyDec, scaleFactor uint32) math.LegacyDec {
return price.Mul(math.LegacyNewDec(10).Power(uint64(scaleFactor)))
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

❓ Verification inconclusive

Overflow / precision-loss risk in GetScaledPrice

Scaling the price by 10^scaleFactor inside decimal arithmetic can silently overflow the math.LegacyDec internal sdk.Int if callers pass an unusually large scaleFactor (e.g. user-provided or un-validated values). A defensive upper-bound check (or at least a documented expectation) would prevent hard-to-trace panics.

 func GetScaledPrice(price math.LegacyDec, scaleFactor uint32) math.LegacyDec {
-	return price.Mul(math.LegacyNewDec(10).Power(uint64(scaleFactor)))
+	if scaleFactor > 18 { // 10^18 ≈ 1e18 keeps us within 256-bit Int range
+		panic("scaleFactor too large – would overflow")
+	}
+	return price.Mul(math.LegacyNewDec(10).Power(uint64(scaleFactor)))
 }

This will help us verify how Power is implemented and whether overflow checks exist.


🏁 Script executed:

#!/bin/bash
set -e

echo "=== Inspect chain/exchange/types/derivative_orders.go ==="
grep -R "GetScaledPrice" -n chain/exchange/types/derivative_orders.go || true
sed -n '1,50p' chain/exchange/types/derivative_orders.go || true

echo
echo "=== Locate LegacyNewDec definition ==="
file=$(rg -l "LegacyNewDec" -n . | head -n1)
echo "File: $file"
sed -n '1,200p' "$file" || true

echo
echo "=== Inspect Power method in the same file ==="
grep -R "Power(" -n "$file" || true
sed -n '200,300p' "$file" || true

Length of output: 10522


Unbounded exponent in GetScaledPrice may cause overflow or excessive computation

chain/exchange/types/derivative_orders.go:9

GetScaledPrice does

return price.Mul(math.LegacyNewDec(10).Power(uint64(scaleFactor)))

without validating scaleFactor. Extremely large exponents can produce very large underlying integers (or overflow if the sdk.Int implementation is bounded), leading to panics or severe performance degradation. Please verify that callers cannot supply untrusted or unbounded values, and consider one of:

  • Adding an explicit upper‐bound check (e.g. if scaleFactor > 18 { panic(...) }), or
  • Documenting the maximum allowed scaleFactor (e.g. “must be ≤18”) for all callers.
🤖 Prompt for AI Agents
In chain/exchange/types/derivative_orders.go around lines 9 to 11, the
GetScaledPrice function multiplies price by 10 raised to scaleFactor without
validating scaleFactor, risking overflow or excessive computation. To fix this,
add an explicit upper-bound check on scaleFactor (for example, panic or return
an error if scaleFactor exceeds 18) before performing the power operation, or
alternatively, document clearly that scaleFactor must not exceed a safe maximum
like 18 to ensure callers do not pass untrusted or unbounded values.

@aarmoa aarmoa merged commit 0495311 into dev May 22, 2025
5 of 6 checks passed
@aarmoa aarmoa deleted the feat/add_exchange_v2_support branch May 22, 2025 19:23
@aarmoa aarmoa changed the title Feat/add exchange v2 support [CP-127] add exchange v2 support May 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants